using System;
using System.Collections.Generic;
using System.Text;
using JobShopModel.Model.FactoryLvl;
using JobShopModel.Events;
using System.Windows.Forms;
using System.Threading;
using JobShopModel.Model.JobLvl;
using JobShopModel.Model.Station;

namespace JobShopModel.Model
{
    /// <summary>
    /// The class representing the entire simulation
    /// </summary>
    public class Simulation
    {
        private int sleepTime;
        private Time timeOfSimulation;
        private Factory factory;
        private SimulationStates state;
        private MainFrm graphicInterface;
        private Thread eventLoopThread;

        /// <summary>
        /// Default constructor - instantiates the factory
        /// </summary>
        public Simulation()
        {
            //Get the factory or create it for the fist time
            this.factory = Factory.getInstance();
        }

        public void InitSimulation(MainFrm graphicInterface, Time timeOfSimulation,int[] numMachines)
        {

                //Adding the Objserver to the factory
                this.graphicInterface = graphicInterface;

                //Set the speed to 0
                this.sleepTime = 0;

                //Set the stations of the Factory
                factory.initStationsList(numMachines);
                
                //Setting the list of jobs            
                this.timeOfSimulation = timeOfSimulation;
                factory.initJobList(timeOfSimulation);

                //Change status of the simulation
                this.state = SimulationStates.INITIALIZED;

                //Creade the simulation thread
                this.eventLoopThread = new Thread(new ThreadStart(this.simulation));
                this.eventLoopThread.Name = "EventLoop_Thread";
           
        }

        /// <summary>
        /// Pauses the simulation
        /// </summary>
        public void PauseSimulation()
        {
            this.state = SimulationStates.PAUSED;
        }

        public void RunSimulation()
        {

            if (eventLoopThread.ThreadState == ThreadState.Unstarted)
            {
                eventLoopThread.Start();
                //this.state = SimulationStates.RUNNING;
            }
            else
            {
                lock (this)
                {
                    this.state = SimulationStates.RUNNING;
                    Monitor.PulseAll(this);
                }
            }
        }

        /// <summary>
        /// Terminates the thread that is processing the events (ending the simulation)
        /// </summary>
        public void EndSimulation()
        {
            this.state = SimulationStates.CANCELED;
        }

        /// <summary>
        /// Main Loop,
        /// Provide the logic of a discrete simulation
        /// </summary>
        private void simulation()
        {
            lock(this){

                //throw an exception if the simulation have not been initialized
                if (this.state != SimulationStates.INITIALIZED)
                {
                    graphicInterface.ShowError(new Exception("Error: The simulation must be initialized !"));
                }

                //Main loop for the simulation:
                while (factory.CurrentTime < timeOfSimulation && !factory.EventQueue.isEmpty() && state != SimulationStates.CANCELED)
                {

                    //Get the next Event
                    IEvent evenement = factory.EventQueue.nextEvent();

                    //Set the clock to the 
                    this.factory.CurrentTime = evenement.ExecTime;

                    
                    try
                    {
                        //Do the processing
                        evenement.Process();
                        //Update the GUI
                        graphicInterface.UpdateGUI(computeStatistics());
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.StackTrace);
                        graphicInterface.ShowError(e);
                    }

                    // Logger.GetLogger().InsertElement(new LogItem(ItemPriorities.DEBUG, factory.CurrentTime, evenement.ToString()));

                    Thread.Sleep(sleepTime);

                    //Handle the speed of the simulation and if paused
                    //Release the lock and wait untill a pulse...
                    if (state == SimulationStates.PAUSED)
                    {
                        Monitor.Wait(this);
                    }
                    

                }
                //Simulation is finished
                this.state = SimulationStates.FINISHED;
                //Update the GUI
                graphicInterface.UpdateGUI(computeStatistics());
            }
        }

        private Statistics computeStatistics()
        {
            Statistics st = new Statistics();

            Decimal currentHoursDecimal = (Decimal)factory.CurrentTime.Days * 24 + factory.CurrentTime.Hours;
            
            //Set the state of the Sim
            st.SimState = this.state;

            //Calculate the progress of the sim:
            Decimal hours2 = (Decimal)timeOfSimulation.Days * 24 + timeOfSimulation.Hours;
            st.SimProgress = factory.CurrentTime / timeOfSimulation;
          
            //Trnaposrt Utilisation & avg time5
            st.TranspTime = factory.Transporter.AvgTranspTime;
            st.TranspUtil = factory.Transporter.Utilisation;
            st.NumTransp = factory.Transporter.Transportations;
            
            //Jobs:
            //get the Queue Delay
            for (int i = 0; i < factory.JobStatistics.JobTypeNumber; i++ )
            {
                st.setJobQueueDelay(i,factory.JobStatistics.AvgDelayQueueStation[(JobTypes)i]);
                st.setJobTransDelay(i,factory.JobStatistics.AvgTransporterDelay[(JobTypes)i]);
                st.setJobFifoDelay(i,factory.JobStatistics.AvgDelayFifoTransp[(JobTypes)i]);
                st.NumOperation[i] = factory.JobStatistics.JobCounter[(JobTypes)i];
                st.JobCreatedCounter[i] = factory.JobStatistics.JobCreatedCounter[(JobTypes)i];
            }
          
            foreach( IStations station in factory.Stations.Values)
            {
                if (station.Name != StationsName.IOSTATION)
                {
                    st.setMachBlocked((int)station.Name, ((RegularStation)station).AvgBlockedTime);
                    st.setMachIdle((int)station.Name, ((RegularStation)station).AvgIddleTime);
                    st.setMachWorking((int)station.Name,((RegularStation)station).AvgOperatingTime);
                    st.setMachNumQueue((int)station.Name,((RegularStation)station).QueueCounter);
                    st.setMachQueueDelay((int)station.Name, ((RegularStation)station).AvgWaitingQueue);
                    st.NumOperation[(int)station.Name] = ((RegularStation)station).JobCounter;
                }
            }
            


            return st;
        }


        #region Properties

        /// <summary>
        /// Gets or sets the simulation's state
        /// </summary>
        public SimulationStates Status
        {
            get { return this.state; }
            set { this.state = value; }
        }

        /// <summary>
        /// Sets the speed of the display (the time it pauses between messages as well as
        /// how many messages are displayed)
        /// </summary>
        public int Speed
        {
            set
            {
                switch (value)
                    {
                        case 0:
                            this.sleepTime = 150;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.SLOW;
                            break;
                        case 1:
                            this.sleepTime = 100;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.NORMAL;
                            break;
                        case 2:
                            this.sleepTime = 10;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.NORMAL;
                            break;
                        case 3:
                            this.sleepTime = 0;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.CREATION;
                            break;
                        case 4:
                            this.sleepTime = 0;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.URGENT;
                            break;
                        default:
                            this.sleepTime = 0;
                            Logger.GetLogger().DefaultPriority = ItemPriorities.NONE;
                            break;
                    }
                }
        }



        #endregion Properties
    }

    /// <summary>
    /// Represent the states of the simulation
    /// </summary>
    /// <seealso cref="Factory"/>
    public enum SimulationStates
    {
        /// <summary>
        /// After the factory is created
        /// The Simulation is ready to be run
        /// </summary>
        INITIALIZED,

        /// <summary>
        /// The Simulation is currently working
        /// </summary>
        RUNNING,

        /// <summary>
        /// The simulation is paused
        /// </summary>
        PAUSED,

        /// <summary>
        /// The simulation is finished
        /// </summary>
        FINISHED,
        CANCELED,
        UNITIALIZED
    }

}
